/**
 * Workload Generator
 */
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <semaphore.h>
#include "cJSON.h"
#include "generator.h"
#include "random.h"
#include "resource_model.h"

int threads_ready = 0;
sem_t threads_ready_mutex;
sem_t threads_start_mutex;
void setup_rendezvous();

void setup_generator() {
  printf("Setting up the Workload Generator.\n");
  setup_random_environment();
  setup_rendezvous();
}

void setup_rendezvous() {
  sem_init(&threads_ready_mutex, 0, 1);
  sem_init(&threads_start_mutex, 0 ,0);
}

cJSON * parse_config() {
  FILE *f= fopen("../conf/config.json","rb");
  fseek(f,0,SEEK_END);
  long len=ftell(f);
  fseek(f,0,SEEK_SET);
  char *data=malloc(len+1);
  fread(data,1,len,f);
  fclose(f);
  cJSON *config = cJSON_Parse(data);
  free(data);
  return config;
}

int workload_generator(cJSON *config, char *resource, resource_model *(*init)(cJSON *), pthread_t *thread)
{
  cJSON *p;
  for(p = config->child; p != NULL; p = p->next)
    {
      //Take a look
      if(strcmp(resource, p->child->valuestring) == 0)
	{
	  break;
	}
    }
  if(NULL == p) 
    {
      return 0;
    }
  void *model =(void *) init(p);
  pthread_create(thread, NULL, thr_resource, model);
}

/**
 * Acts as the controller for each resource thread
 */
void * thr_resource(void *config)
{ 
  setup_schedule();
  struct timespec req={0};
  struct timeval current_time = {0};
  resource_model *model = (resource_model *)config;
  if(model == NULL) {
    perror("No Model Given\n");
    return;
  }
  
  //Enforce a Rendezvous before threads begin
  sem_wait(&threads_ready_mutex);
  if(++threads_ready == CNT_RESOURCES) {
    printf("Workload Generator Starting..\n");
    int i = 0;
    for(i; i < CNT_RESOURCES; i++) {
      sem_post(&threads_start_mutex);
    }
  }
  sem_post(&threads_ready_mutex);
  sem_wait(&threads_start_mutex);
  
  /* Start generating a workload for this resource */
  gettimeofday(&current_time,NULL);
  model->birth(model);
  
  while(has_event()) {
    event *e = next_event();
    unsigned long time_to_sleep = timeval_diff(&current_time, e->timestamp);
    usleep(time_to_sleep);
    gettimeofday(&current_time,NULL);
    e->job(e->args);
    free(e->timestamp);
    free(e);
  }
}

/**
 * Gets the point in time that is ms milliseconds from now
 */
struct timeval *ms_from_now(double ms){
  struct timeval *t = malloc(sizeof(struct timeval));
  gettimeofday(t,NULL);
  unsigned int seconds = ms / 1000;
  unsigned int microseconds = (ms - (seconds*1000)) * 1000;
  unsigned int total_microseconds = (t->tv_usec + microseconds) % 1000000;
  unsigned int seconds_to_add = (t->tv_usec + microseconds) / 1000000;
  t->tv_sec += seconds + seconds_to_add;
  t->tv_usec = total_microseconds;
  return t;
}

/**
 * Get the difference in milliseconds between 2 times
 */
unsigned long timeval_diff(struct timeval *now, struct timeval *future)
{
  if(future->tv_sec < now->tv_sec) {
    return 0;
  }
  else if(future->tv_sec == now->tv_sec && future->tv_usec < now->tv_usec) {
    return 0;
  }
  unsigned long usec;
  usec=(future->tv_sec-now->tv_sec)*1000000;
  usec+=(future->tv_usec-now->tv_usec);
  return usec;
}
